﻿using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using d_rail.lib.Model;

namespace d_rail.lib.Parsers
{
    public class TrackParser
    {
        public IList<Track> TrackList;
        private Dictionary<string, Connections> _connections;
        private Dictionary<string, Connections> _stations;
        private Dictionary<string, Connections> _ends;

        public void ClearTracks()
        {
            Contract.Ensures(TrackList == null);
            TrackList = null;
        }

        public IList<Track> Parse(string path)
        {
            Contract.Requires(!string.IsNullOrEmpty(path));
            Contract.Requires(File.Exists(path));
            Contract.Requires(TrackList == null);
            Contract.Ensures(Contract.Result<IList<Track>>() != null);
            Contract.Ensures(Contract.Result<IList<Track>>().Any());

            TrackList = new List<Track>();
            var trackMap = new Dictionary<string,int>();
            _connections = new Dictionary<string, Connections>();
            _stations = new Dictionary<string, Connections>();
            _ends = new Dictionary<string, Connections>();
            
            using (var file = new StreamReader(path))
            {
                string line;
                while ((line = file.ReadLine()) != null)
                {
                    Contract.Assert(!trackMap.ContainsKey(line));
                    trackMap.Add(line, 42);

                    var tokens = TokenizeLine(line);
                    Contract.Assert(tokens.Length >= 2);
                    if (tokens.Length > 2)
                        Contract.Assert(tokens[1] != tokens[2]);

                    if (tokens.Length == 3 && tokens[0] == "CONN")
                    {
                        CreateConnection(tokens);
                    }
                    else if (tokens.Length == 3 && tokens[0] == "STAT")
                    {
                        CreateStation(tokens);
                    }
                    else if (tokens.Length == 2 && tokens[0] == "END")
                    {
                        CreateEnd(tokens);
                    }
                }

                foreach (var s in _stations)
                {
                    var track = CreateTrackFromConnection(s);
                    TrackList.Add(track);
                }

                foreach (var c in _connections)
                {
                    if (c.Value.SourceIds.Count + c.Value.TargetIds.Count == 1)
                        continue;
                    var track = CreateTrackFromConnection(c);
                    TrackList.Add(track);
                }

                foreach (var e in _ends)
                {
                    Contract.Assert(_connections[e.Key].SourceIds.Count + _connections[e.Key].TargetIds.Count == 1);
                    var track = CreateTrackFromConnection(e);
                    TrackList.Add(track);
                }
            }

            return TrackList;
        }

        private void CreateConnection(IList<string> tokens)
        {
            Contract.Requires(null != tokens);
            Contract.Requires(tokens.Count == 3);
            Contract.Requires(tokens[0] == "CONN");
            if (!_connections.ContainsKey(tokens[1]))
                _connections.Add(tokens[1], new Connections());
            if (!_connections.ContainsKey(tokens[2]))
                _connections.Add(tokens[2], new Connections());
            Contract.Assert(!_connections[tokens[1]].TargetIds.Contains(tokens[2]));
            _connections[tokens[1]].TargetIds.Add(tokens[2]);
            Contract.Assert(!_connections[tokens[2]].SourceIds.Contains(tokens[1]));
            _connections[tokens[2]].SourceIds.Add(tokens[1]);
        }

        private void CreateStation(IList<string> tokens)
        {
            Contract.Requires(null != tokens);
            Contract.Requires(tokens.Count == 3);
            Contract.Requires(tokens[0] == "STAT");
            Contract.Assert(!_stations.ContainsKey(tokens[2]));
            var count = _stations.Count;
            Contract.Assume(_stations.Count == count);

            _stations.Add(tokens[2], new Connections
            {
                SourceIds = new List<string> { tokens[2] },
                TargetIds = new List<string> { tokens[2] }
            });
            Contract.Assume(_stations.Count == count + 1);
        }

        private void CreateEnd(IList<string> tokens)
        {
            Contract.Requires(tokens.Count == 2);
            Contract.Requires(tokens[0] == "END");
            Contract.Assert(!_ends.ContainsKey(tokens[1]));
            var count = _ends.Count;
            Contract.Assume(_ends.Count == count);
            _ends.Add(tokens[1], new Connections
            {
                SourceIds = new List<string> { " " },
                TargetIds = new List<string>()
            });
            Contract.Assume(_ends.Count == count + 1);
        }

        private Track CreateTrackFromConnection(KeyValuePair<string, Connections> c)
        {
            Contract.Requires(c.Value.SourceIds.Count + c.Value.TargetIds.Count < 4);
            Contract.Requires(c.Value.SourceIds.Count + c.Value.TargetIds.Count > 0);
            Contract.Requires(c.Value.TargetIds.Count <= 2);
            Contract.Requires(c.Value.SourceIds.Count <= 2);
            Contract.Ensures(Contract.Result<Track>() != null);

            var id = c.Key;
            var connections = c.Value;

            if (connections.SourceIds.Count + connections.TargetIds.Count == 3)
            {
                return CreateSwitch(connections, id);
            }
            if (connections.SourceIds.Count + connections.TargetIds.Count == 2)
            {
                return CreateStraightOrStation(connections, id);
            }
            if (connections.SourceIds.Count + connections.TargetIds.Count == 1 &&
                (connections.SourceIds.Count == 1 || connections.TargetIds.Count == 1))
            {
                //When we herp, we derp
                Contract.Assert((c.Value.SourceIds.Count == 0 && c.Value.TargetIds.Count == 1) ||
                                (c.Value.TargetIds.Count == 0 && c.Value.SourceIds.Count == 1));

                return CreateEnd(id);
            }

            Contract.Assume(Contract.Result<Track>() != null);
            return null;
        }

        private Track CreateStraightOrStation(Connections connections, string id)
        {
            Contract.Requires(!string.IsNullOrEmpty(id));
            Contract.Requires(null != connections);
            Contract.Requires(connections.SourceIds.Count + connections.TargetIds.Count == 2);
            Contract.Ensures(Contract.Result<Track>() != null);
            Contract.Ensures(Contract.Result<Track>() is Station || Contract.Result<Track>() is Straight);

            var track = new Straight();
            //Straight or Station
            if (_stations.ContainsKey(connections.SourceIds[0]))
            {
                track = new Station
                {
                    SourceId = connections.SourceIds[0],
                    TargetId = connections.TargetIds[0],
                    Id = id
                };
            }
            else
            {
                track.Id = id;
                track.SourceId = connections.SourceIds[0];
                track.TargetId = connections.TargetIds[0];
            }

            Contract.Assume(track.Id == id);
            Contract.Assume(null != track.SourceId);
            Contract.Assume(null != track.TargetId);

            return track;
        }

        private static End CreateEnd(string id)
        {
            Contract.Requires(!string.IsNullOrEmpty(id));
            Contract.Ensures(Contract.Result<End>() != null);
            Contract.Ensures(Contract.Result<End>().SourceId == id);
            Contract.Ensures(Contract.Result<End>().Id == id);
            var end = new End
            {
                SourceId = id,
                Id = id
            };
            return end;
        }

        private static Switch CreateSwitch(Connections connections, string id)
        {
            Contract.Requires(!string.IsNullOrEmpty(id));
            Contract.Requires(null != connections);
            Contract.Requires(connections.SourceIds.Count + connections.TargetIds.Count == 3);
            Contract.Ensures(Contract.Result<Switch>() != null);
            Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<Switch>().Id));
            Contract.Ensures(Contract.Result<Switch>().SourceId != null);
            Contract.Ensures(Contract.Result<Switch>().TargetId1 != null);
            Contract.Ensures(Contract.Result<Switch>().TargetId2 != null);
            var track = new Switch { Id = id };
            if (connections.SourceIds.Count > connections.TargetIds.Count)
            {
                track.TargetId1 = connections.SourceIds[0];
                track.TargetId2 = connections.SourceIds[1];
                track.SourceId = connections.TargetIds[0];
            }
            else
            {
                track.SourceId = connections.SourceIds[0];
                track.TargetId1 = connections.TargetIds[0];
                track.TargetId2 = connections.TargetIds[1];
            }
            return track;
        }

        private static string[] TokenizeLine(string line)
        {
            Contract.Requires(null != line);
            Contract.Ensures(Contract.Result<string[]>() != null);
            Contract.Ensures(Contract.Result<string[]>().Any());

            //trim spaces
            line = Regex.Replace(line, @"\s+", " ");
            //split
            return line.Split(' ');
        }
    }
}
